/*
  gcc `pkg-config fuse --cflags --libs` -o exploit.hello exploit.hello.c
*/

#define FUSE_USE_VERSION 26

#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <unistd.h>

#define PATH_L "/license."
#define PATH_I "/insns."
#define PATH_B "/logbuf."

static size_t insns_size;
static char * insns;

static int hello_getattr(const char *path, struct stat *stbuf)
{
    memset(stbuf, 0, sizeof(struct stat));

    if (strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0700;
        stbuf->st_nlink = 2;

    } else if (strncmp(path, PATH_L, sizeof(PATH_L)-1) == 0) {
        stbuf->st_mode = S_IFREG | 0400;
        stbuf->st_nlink = 1;
        stbuf->st_size = 1;

    } else if (strncmp(path, PATH_I, sizeof(PATH_I)-1) == 0) {
        stbuf->st_mode = S_IFREG | 0400;
        stbuf->st_nlink = 1;
        stbuf->st_size = insns_size;

    } else if (strncmp(path, PATH_B, sizeof(PATH_B)-1) == 0) {
        stbuf->st_mode = S_IFREG | 0600;
        stbuf->st_nlink = 1;
        stbuf->st_size = 1;

    } else return -ENOENT;

    return 0;
}

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
             off_t offset, struct fuse_file_info *fi)
{
    (void) offset;
    (void) fi;

    if (strcmp(path, "/") != 0) return -ENOENT;

    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);

    return 0;
}

static int hello_open(const char *path, struct fuse_file_info *fi)
{
    if (strncmp(path, PATH_L, sizeof(PATH_L)-1) == 0) {
        if ((fi->flags & 3) != O_RDONLY) return -EACCES;

    } else if (strncmp(path, PATH_I, sizeof(PATH_I)-1) == 0) {
        if ((fi->flags & 3) != O_RDONLY) return -EACCES;

    } else if (strncmp(path, PATH_B, sizeof(PATH_B)-1) == 0) {
        if ((fi->flags & 3) != O_RDWR) return -EACCES;

    } else return -ENOENT;

    return 0;
}

#define return_error() do { \
    fprintf(stderr, "error in %s: %u\n", __func__, __LINE__); \
    raise(SIGTERM); \
    return(-EIO); \
} while (0)

static int hello_read(const char *path, char *buf, size_t size, off_t offset,
              struct fuse_file_info *fi)
{
    (void) fi;

    enum {
        INIT_X = -'X',
        READ_L = -'L',
        READ_I = -'I',
        READ_B = -'B',
    };
    static size_t state = INIT_X;

    const char * const dot = strchr(path, '.');
    if (!dot) return_error();
    char * end = NULL;
    const size_t index = strtoul(dot + 1, &end, 10);
    if (end <= dot + 1 || *end) return_error();

    if (strncmp(path, PATH_L, sizeof(PATH_L)-1) == 0) {
        static int first = 1;
        if (first) {
            first = 0;
            if (state != INIT_X) return_error();
            state = READ_L;
            if (write(0, &state, sizeof(state)) != sizeof(state)) return_error();
        }
        if (state != READ_L) return_error();
        if (offset != 0) return_error();
        if (size < 1) return_error();
        size = 1;
        *buf = 0;

    } else if (strncmp(path, PATH_I, sizeof(PATH_I)-1) == 0) {
        static int first = 1;
        if (first) {
            first = 0;
            if (state != READ_L) return_error();
            state = READ_I;
            if (write(0, &state, sizeof(state)) != sizeof(state)) return_error();
        }
        if (state != READ_I) return_error();
        if (offset != 0) return_error();
        if (size < insns_size) return_error();
        size = insns_size;
        memcpy(buf, insns, size);

    } else if (strncmp(path, PATH_B, sizeof(PATH_B)-1) == 0) {
        static int first = 1;
        if (first) {
            first = 0;
            if (state != READ_I) return_error();
            state = READ_B;
            if (write(0, &state, sizeof(state)) != sizeof(state)) return_error();
        }
        if (state != READ_B) return_error();
        if (offset != 0) return_error();
        if (size < 1) return_error();
        size = 1;
        *buf = 0;

    } else return_error();

    if (write(0, &index, sizeof(index)) != sizeof(index)) return_error();
    size_t check = -1;
    if (read(0, &check, sizeof(check)) != sizeof(check)) return_error();
    if (check != index) return_error();

    return size;
}

static int hello_write(const char *path, const char *buf, size_t size, off_t offset,
              struct fuse_file_info *fi)
{
    (void) buf;
    (void) fi;

    printf("hello_write %s size %zu offset %lld\n", path, size, (long long)offset);

    return size;
}

static struct fuse_operations hello_oper = {
    .getattr = hello_getattr,
    .readdir = hello_readdir,
    .open    = hello_open,
    .read    = hello_read,
    .write   = hello_write,
};

int main(int argc, char *argv[])
{
    if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0)) return_error();

    if (read(0, &insns_size, sizeof(insns_size)) != sizeof(insns_size)) return_error();
    if (insns_size <= 0) return_error();
    if (insns_size >= INT_MAX) return_error();

    insns = malloc(insns_size);
    if (!insns) return_error();
    if (read(0, insns, insns_size) != insns_size) return_error();

    return fuse_main(argc, argv, &hello_oper, NULL);
}
